/* hexedit -- Hexadecimal Editor for Binary Files
   Copyright (C) 1998 Pixel (Pascal Rigaux)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/

#include "bookmarks.h"
#include "crule.h"
#include "dhexview.h"
#include "misc.h"

#include <assert.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>


/*******************************************************************************/
/* Global variables */
/*******************************************************************************/
INT lastEditedLoc, biggestLoc, fileSize;
INT mark_min, mark_max, mark_set;
INT base, oldbase;
int normalSpaces, cursor, cursorOffset, hexOrAscii;
int cursor, blocSize, lineLength, colsUsed, page;
int isReadOnly, fd, nbBytes, oldcursor, oldattr, oldcursorOffset;
int sizeCopyBuffer, *bufferAttr;
char *progName, *fileName, *baseName;
unsigned char *buffer, *copyBuffer;
typePage *edited;

char *lastFindFile = NULL, *lastYankToAFile = NULL, *lastAskHexString =
    NULL, *lastAskAsciiString = NULL, *lastFillWithStringHexa =
    NULL, *lastFillWithStringAscii = NULL;

hstack_t *position_history;

struct dhexview_options dhexview_options = {
    true,
    0x1000,
    true,
    COLORED_PANE_BOTH,
    false,
};


#ifdef HAVE_COLORS /* [ */

crulist_t *crulist;
crulist_t *hlsearch_crulist;
color_pairs_t color_pairs;

#define CW printf

static int
str2color (const char *color)
{
    if (!color)
        return -1;

#define STR2COLOR(_s_) (0 == strcasecmp(color, #_s_)) return COLOR_##_s_

         if STR2COLOR(BLACK);
    else if STR2COLOR(RED);
    else if STR2COLOR(GREEN);
    else if STR2COLOR(YELLOW);
    else if STR2COLOR(BLUE);
    else if STR2COLOR(MAGENTA);
    else if STR2COLOR(CYAN);
    else if STR2COLOR(WHITE);
    else return -1;
}

static int
l_color_rule (lua_State *L)
{
    if (!lua_istable(L, -1)) {
        printf("Invalid color rule\n");
        return 0;
    }

    lua_getfield(L, -1, "type");
    const char *type = lua_tostring(L, -1);
    crule_t *rule;

    if (0 == strcasecmp(type, "exact_string") ||
        0 == strcasecmp(type, "regex"))
    {
        bool string = false;
        const char *prop = NULL;

        if (0 == strcasecmp(type, "exact_string")) {
            prop = "str";
            string = true;
            rule = crule_new(CRULE_TYPE_EXACT);
        } else if (0 == strcasecmp(type, "regex")) {
            prop = "regex";
            rule = crule_new(CRULE_TYPE_REGEX);
        } else {
            assert(0);
        }

        lua_pop(L, 1);

        char *buf;
        int bufsz;

        lua_getfield(L, -1, prop);
        if (lua_isstring(L, -1)) {
            buf = strdup(lua_tostring(L, -1));
            bufsz = strlen(buf);
            lua_pop(L, 1);
            //printf("string: `%s'\n", buf);
        } else if (lua_istable(L, -1)) {
            bufsz = luaL_getn(L, -1);
            //printf("table sz: %d\n", bufsz);
            buf = malloc(bufsz);
            int i;
            for (i = 0; i < bufsz; ++i) {
                lua_rawgeti(L, -1, i + 1);
                int c = lua_tonumber(L, -1);
                lua_pop(L, 1);
                if (c > 0xFF || c < 0)
                    CW("Byte value not in range: %d\n", c);
                buf[i] = (char) c;
            }
            lua_pop(L, 1);
        } else {
            printf("invalid\n");
            return 0;
        }

        if (string)
            rule->set_exact_string(rule, (uint8_t *) buf, bufsz);
        else
            rule->set_regex(rule, buf, bufsz, 0);

    } else if (0 == strcasecmp(type, "absolute_position") ||
               0 == strcasecmp(type, "periodic_position"))
    {
        if (0 == strcasecmp(type, "absolute_position"))
            rule = crule_new(CRULE_TYPE_ABSOLUTE);
        else if (0 == strcasecmp(type, "periodic_position"))
            rule = crule_new(CRULE_TYPE_PERIODIC);
        else
            assert(!"Programmer error -- invalid type");
        lua_pop(L, 1);

        lua_getfield(L, -1, "position");
        uint64_t position = lua_tonumber(L, -1);
        lua_pop(L, 1);

        rule->set_position(rule, position);
    } else {
        printf("`%s' is an invalid color rule type\n", type);
        return 0;
    }

    lua_getfield(L, -1, "colors");
    if (!lua_istable(L, -1)) {
        printf("`colors' is not a table\n");
        return 0;
    }

    int ncolors = luaL_getn(L, -1);
    int i;

    for (i = 0; i < ncolors; ++i) {
        lua_rawgeti(L, -1, i + 1);
        if (!lua_istable(L, -1)) {
            printf("Entry %d in colors table is not a table itself\n", i + 1);
            lua_pop(L, 1);
            continue;
        }

        int from, to, fg, bg, sz;

        lua_getfield(L, -1, "from");
        if (lua_isnumber(L, -1)) {
            from = lua_tonumber(L, -1);
        } else {
            if (!lua_isnil(L, -1))
                printf("Entry %d: `from' is not a number\n", i + 1);
            from = 0;
        }
        lua_pop(L, 1);

        lua_getfield(L, -1, "to");
        if (lua_isnumber(L, -1)) {
            to = lua_tonumber(L, -1);
        } else {
            if (!lua_isnil(L, -1))
                printf("Entry %d: `to' is not a number\n", i + 1);
            to = 0;
        }
        lua_pop(L, 1);

        if (to) {
            sz = to - from;
        } else {
            lua_getfield(L, -1, "sz");
            if (lua_isnumber(L, -1)) {
                sz = lua_tonumber(L, -1);
            } else {
                if (!lua_isnil(L, -1))
                    printf("Entry %d: `sz' is not a number\n", i + 1);
                sz = 0;
            }
            lua_pop(L, 1);
        }

        lua_getfield(L, -1, "fg");
        fg = str2color(lua_tostring(L, -1));
        lua_pop(L, 1);

        lua_getfield(L, -1, "bg");
        if (lua_isnil(L, -1))
            bg = str2color("default");
        else
            bg = str2color(lua_tostring(L, -1));
        lua_pop(L, 1);

        rule->add_color(rule, from, sz, fg, bg);

        lua_pop(L, 1);
    }

    crulist->add(crulist, rule);

    return 0;
}
#endif /* ] HAVE_COLORS */

static int
l_options (lua_State *L)
{
    if (!lua_istable(L, -1)) {
        printf("Invalid color rule\n");
        return 0;
    }

    lua_pushnil(L);
    while (0 != lua_next(L, -2)) {
        const char *var = lua_tostring(L, -2);

        if (0 == strcasecmp(var, "use_color")) {
            colored = lua_toboolean(L, -1);
        } else if (0 == strcasecmp(var, "color_between_bytes")) {
            dhexview_options.color_between_bytes = lua_toboolean(L, -1);
        } else if (0 == strcasecmp(var, "page_size")) {
            dhexview_options.page_size = lua_tonumber(L, -1);
        } else if (0 == strcasecmp(var, "hlsearch")) {
            dhexview_options.hlsearch = lua_toboolean(L, -1);
        } else if (0 == strcasecmp(var, "colored_pane")) {
            const char *str = lua_tostring(L, -1);
            if (0 == strcasecmp(str, "left"))
                dhexview_options.colored_pane = COLORED_PANE_LEFT;
            else if (0 == strcasecmp(str, "right"))
                dhexview_options.colored_pane = COLORED_PANE_RIGHT;
            else if (0 == strcasecmp(str, "both"))
                dhexview_options.colored_pane = COLORED_PANE_BOTH;
            else
                printf("`%s' is not a valid 'colored_pane' option\n", str);
        } else {
            printf("`%s' is not a known option\n", var);
        }

        lua_pop(L, 1);
    }

    return 0;
}

static int
l_read (lua_State *L)
{
    if (!(lua_isnumber(L, -1) && lua_isnumber(L, -2))) {
        error(L, "`read' takes two arguments: offset and size, both numbers");
        return 0;
    }

    int size = lua_tonumber(L, -1);
    int offset = lua_tonumber(L, -2);
    lua_pop(L, 2);

    char *buf = malloc(size);

    LSEEK(fd, offset);      // FIXME: error checking?
    int nread = read(fd, buf, size);

    if (-1 == nread) {
        error(L, "read operation failed: %s", strerror(errno));
        return 0;
    }

    lua_pushlstring(L, buf, (size > nread ? nread : size));

    free(buf);

    return 1;
}

int colored = false;

static void
read_config_file (lua_State *lua)
{
    if (!lua) {
        fprintf(stderr, "Could not open lua\n");
        exit(1);
    }

    char path[2048];
    strcpy(path, getenv("HOME"));
    strcat(path, "/.dhexviewrc");

    struct stat st;
    if (-1 == stat(path, &st)) {
        return;
    }

    if (luaL_dofile(lua, path)) {
        fprintf(stderr, "Error parsing config file\n");
    }

    return;
}

static char *history_file;
static lua_State *L;

static void
read_in_file_history (lua_State *L, const char *fileName)
{
    char dir[PATH_MAX * 2];

    int sz = snprintf(dir, sizeof(dir), "%s/.dhexview", getenv("HOME"));
    if (sz > sizeof(dir)) {
        fprintf(stderr, "Your home directory path is very long.\n");
        return;
    }

    struct stat st;
    if (-1 == stat(dir, &st)) {
        if (ENOENT != errno) {
            fprintf(stderr, "Could not stat %s: %s\n", dir, strerror(errno));
            return;
        }

        if (-1 == mkdir(dir, 0700)) {
            fprintf(stderr, "Could mkdir %s: %s\n", dir, strerror(errno));
            return;
        }
    }

    char *filename;
    int flen;
    if ('/' != fileName[0]) {
        char cwd[PATH_MAX];
        if (!getcwd(cwd, sizeof(cwd))) {
            fprintf(stderr, "Could not get CWD: %s\n", strerror(errno));
            return;
        }
        int cwdsz = strlen(cwd);

        flen = cwdsz + sizeof('/') + strlen(fileName) + sizeof('\0');
        filename = calloc(1, flen);
        sz = snprintf(filename, flen, "%s/%s", cwd, fileName);
        if (sz > flen) {
            free(filename);
            fprintf(stderr, "Weird.\n");
            return;
        }
    } else {
        filename = strdup(fileName);
        flen = strlen(filename);
    }

    canon_filename(filename);

    /* Now replace all slashes with dashes */
    char *s;
    for (s = filename + 1; *s; ++s)
        if ('/' == *s)
            *s = '-';

    //printf("F: %s\n", filename);

    flen = strlen(dir) + sizeof('/') + flen + sizeof('\0');
    history_file = calloc(1, flen);
    sz = snprintf(history_file, flen, "%s/%s", dir, filename + 1);

    //printf("H: %s\n", history_file);

    free(filename);
    (void) luaL_dofile(L, history_file);

    return;
}

static void
write_out_file_history (lua_State *L)
{
    if (!history_file)
        return;

    lua_getglobal(L, "save_state");
    if (!lua_isfunction(L, -1))
        error(L, "`save_state' is not a function");
    lua_pushstring(L, history_file);

    if (lua_pcall(L, 1, 0, 0) != 0)
        error(L, "error running save_state: %s", lua_tostring(L, -1));

    return;
}

bms_t *bms;

static void
usage (const char *progname)
{
    fprintf(stderr,
"Usage: %s [options] filename\n"
"\n"
"Options:\n"
"    -f file    Read configuration file.  May be specified more than once.\n"
"    -h         Print this screen and exit\n"
    , progname
    );
    return;
}

/*******************************************************************************/
/* main */
/*******************************************************************************/
int main(int argc, char **argv)
{
    progName = basename(argv[0]);

    struct {
        char **fnames;
        int    nelem;
    } cfiles = { NULL, 0, };

    L = lua_open();  
    luaL_openlibs(L);
#include "bookmarks.LC"
#include "init.LC"

    lua_pushcfunction(L, l_options);
    lua_setglobal(L, "options");

    lua_pushcfunction(L, l_read);
    lua_setglobal(L, "read");

#ifdef HAVE_COLORS
    lua_pushcfunction(L, l_color_rule);
    lua_setglobal(L, "color_rule");

    crulist = crulist_new();
    hlsearch_crulist = crulist_new();
    memset(&color_pairs, 0, sizeof(color_pairs));
#endif

    int opt;
    while (-1 != (opt = getopt(argc, argv, "f:h"))) {
        switch(opt) {
            case 'f':
                ++cfiles.nelem;
                cfiles.fnames = realloc(cfiles.fnames,
                                    sizeof(char *) * cfiles.nelem);
                cfiles.fnames[cfiles.nelem - 1] = strdup(optarg);
                break;
            case 'h':
                usage(progName);
                exit(0);
                break;
            default:
            case '?':
                usage(progName);
                exit(1);
                break;
        }
    }

    if (optind >= argc) {
        usage(progName);
        exit(1);
    }

    init();
    fileName = strdup(argv[optind]);
    openFile();

    lua_pushstring(L, argv[optind]);
    lua_setglobal(L, "filename");

    read_config_file(L);

    int i;
    for (i = 0; i < cfiles.nelem; ++i) {
        if (luaL_dofile(L, cfiles.fnames[i])) {
            error(L, "Error parsing file: %s", cfiles.fnames[i]);
            exit(1);
        }
    }

    read_in_file_history(L, fileName);

    initCurses();
    bms = bms_new(L);
    readFile();
    position_history = hstack_new(1000);
    position_history->push(position_history, 0);
    crulist->init_colors(crulist, &color_pairs);
    do
        display();
    while (key_to_function(getch()));
    quit();
    return 0;                   /* for no warning */
}



/*******************************************************************************/
/* other functions */
/*******************************************************************************/
void init(void)
{
    page = 0;                   /* page == 0 means initCurses is not done */
    normalSpaces = 3;
    hexOrAscii = true;
    copyBuffer = NULL;
    edited = NULL;
}

void quit(void)
{
    exitCurses();
    write_out_file_history(L);
    lua_close(L);
    free(fileName);
    free(buffer);
    free(bufferAttr);
    FREE(copyBuffer);
    discardEdited();
    FREE(lastFindFile);
    FREE(lastYankToAFile);
    FREE(lastAskHexString);
    FREE(lastAskAsciiString);
    FREE(lastFillWithStringHexa);
    FREE(lastFillWithStringAscii);
    exit(0);
}
